package es.ise.core.mbg;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.CommonTokenStream;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.JDBCConnectionConfiguration;
import org.mybatis.generator.config.JavaClientGeneratorConfiguration;
import org.mybatis.generator.config.JavaModelGeneratorConfiguration;
import org.mybatis.generator.config.ModelType;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.config.SqlMapGeneratorConfiguration;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.internal.DefaultShellCallback;

import es.ise.core.mbg.parsing.TableConfigurationLexer;
import es.ise.core.mbg.parsing.TableConfigurationParser;
import es.ise.core.mbg.plugins.AutoRenamePlugin;
import es.ise.core.mbg.plugins.ColumnMetadataPlugin;
import es.ise.core.mbg.plugins.ExampleInSubpackagePlugin;
import es.ise.core.mbg.plugins.IdsAsLongPlugin;
import es.ise.core.mbg.plugins.PagingEnabledClientsPlugin;
import es.ise.core.mbg.plugins.RemarksInCommentsPlugin;
import es.ise.core.mbg.plugins.SuperclassesPlugin;
import es.ise.core.mbg.plugins.TableOrViewPlugin;
import es.ise.core.mbg.plugins.AuthoringPlugin;
import es.ise.core.mbg.plugins.RepresentationsPlugin;
import es.ise.core.mbg.plugins.constraints.MaxLengthConstraintPlugin;
import es.ise.core.mbg.plugins.constraints.NullConstraintPlugin;

/**
 * Con esta clase se generan automáticamente todos los ficheros de MyBatis
 * llamando a su método <code>main</code>. * 
 * 
 * @author Paco
 *
 */
public class MyBatisIseGenerator
{
	private static final Pattern identificador = Pattern.compile("^[a-z]([a-z0-9]*)$");
	
	/**
	 * Se inicia la generación de código de MyBatis.
	 * 
	 * Tiene los siguientes argumentos:
	 * <ul>
	 * <li>Nombre corto del proyecto, utilizado para generar los paquetes</li>
	 * <li>Directorio base del proyecto</li>
	 * </ul> 
	 * 
	 * @param args argumentos del método main
	 * @throws IllegalArgumentException si no se indica el directorio o si este no existe 
	 */
	public static void main(String args[])
	{
		if(args.length != 2)
			throw new IllegalArgumentException("Uso: java es.ise.core.mbg.MyBatisIseGenerator <project_name> <project_path>");
		
		// Obtener el nombre del proyecto y validarlo
		String project = args[0];
		Matcher matcher = identificador.matcher(project);
		if(!matcher.find())
			throw new IllegalArgumentException("El nombre del projecto debe estar todo en minúsculas, solo puede tener letras y cifras, y debe empezar por una letra");
		
		File basedir = new File(args[1]);
		
		// Obtener el fichero de tablas y validarlo
		File tablas = new File(basedir, "mybatis/tablas.ise");
		if(!tablas.exists())
			throw new IllegalArgumentException("No existe el archivo de configuración de tablas: "+tablas.getAbsolutePath());
		
		// Obtener el fichero de configuración a través de la ruta, y sus validaciones 
		File configDir = new File(basedir, "WebContent/WEB-INF/config");
		String entorno = System.getProperty("app.env", "produccion");
		File configFile = new File(configDir, String.format("config_%s.properties", entorno));
		if(!configFile.exists())
			throw new IllegalArgumentException(String.format("No existe el fichero de configuración: %s", configFile.getAbsolutePath()));
		
		// Obtener la ruta de fuentes y validarla
		File srcDir = new File(basedir, "src/project");
		if(!configFile.exists())
			throw new IllegalArgumentException(String.format("No existe la ruta de fuentes: %s", srcDir.getAbsolutePath()));
		
		Properties properties = new Properties();
		try
		{
			properties.load(new FileReader(configFile));
		}
		catch (Exception e)
		{
			// no debería llegar aquí en ningún momento...
			e.printStackTrace();
		}
		
		// Crea la configuracion inicial y obtiene el contexto
		Context context = getContext();
		
		// carga los plugins necesarios
		loadPlugins(context);
		
		// establece la conexión con la base de datos
		context.setJdbcConnectionConfiguration(getJdbcConnection(properties));
		
		// configura el modelo java
		context.setJavaModelGeneratorConfiguration(getJavaModel(project, srcDir));
		
		// configura la generacion de sqlMaps
		context.setSqlMapGeneratorConfiguration(getSqlMap(project, srcDir));
		
		// configura la creación de interfaces Mappers
		context.setJavaClientGeneratorConfiguration(getJavaClient(project, srcDir));
		
		// configura las tablas
		setupTable(tablas, context);
		
		// prepare the call
		Configuration configuration = new Configuration();
		configuration.addContext(context);
		List<String> warnings = new ArrayList<String>();
		DefaultShellCallback callback = new DefaultShellCallback(true);
		try
		{
			MyBatisGenerator generator = new MyBatisGenerator(configuration, callback, warnings);
			generator.generate(null);
		}
		catch (InvalidConfigurationException e)
		{
			System.err.println("Error in configuration: "+e.getMessage());
		}
		catch (SQLException e)
		{
			System.err.println("Error fetching data from DB: "+e.getMessage());
		}
		catch (IOException e)
		{
			System.err.println("Error writing or reading files: "+e.getMessage());
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
	}
	
	private static Context getContext()
	{
		Context context = new Context(ModelType.CONDITIONAL);
		context.setId("main");
		context.setTargetRuntime("MyBatis3");
		return context;
	}
	
	private static void loadPlugins(Context context)
	{
		Class<?> plugins[] = new Class<?>[] {
				AutoRenamePlugin.class,
				TableOrViewPlugin.class,
				IdsAsLongPlugin.class,
				ColumnMetadataPlugin.class,
				ExampleInSubpackagePlugin.class,
				SuperclassesPlugin.class,
				RemarksInCommentsPlugin.class,
				RepresentationsPlugin.class,
				NullConstraintPlugin.class,
				MaxLengthConstraintPlugin.class,
				AuthoringPlugin.class,
				PagingEnabledClientsPlugin.class
		};
		for(Class<?> plugin: plugins)
		{
			PluginConfiguration pluginConfig = new PluginConfiguration();
			pluginConfig.setConfigurationType(plugin.getName());
			context.addPluginConfiguration(pluginConfig);
		}
	}
	
	private static JDBCConnectionConfiguration getJdbcConnection(Properties properties)
	{
		JDBCConnectionConfiguration jdbcConfig = new JDBCConnectionConfiguration();
		jdbcConfig.setConnectionURL(properties.getProperty("jdbc.main.url"));
		jdbcConfig.setDriverClass(properties.getProperty("jdbc.main.driver"));
		jdbcConfig.setUserId(properties.getProperty("jdbc.main.user"));
		jdbcConfig.setPassword(properties.getProperty("jdbc.main.pass"));
		return jdbcConfig;
	}
	
	private static JavaModelGeneratorConfiguration getJavaModel(String project, File srcDir)
	{
		JavaModelGeneratorConfiguration javaModelConfig = new JavaModelGeneratorConfiguration();
		javaModelConfig.setTargetPackage(String.format("es.ise.%s.model", project));
		javaModelConfig.setTargetProject(srcDir.getAbsolutePath());
		javaModelConfig.addProperty("trimStrings", "true");
		return javaModelConfig;
	}
	
	private static SqlMapGeneratorConfiguration getSqlMap(String project, File srcDir)
	{
		SqlMapGeneratorConfiguration sqlMapConfig = new SqlMapGeneratorConfiguration();
		sqlMapConfig.setTargetPackage(String.format("es.ise.%s.mappers", project));
		sqlMapConfig.setTargetProject(srcDir.getAbsolutePath());
		return sqlMapConfig;
	}
	
	private static JavaClientGeneratorConfiguration getJavaClient(String project, File srcDir)
	{
		JavaClientGeneratorConfiguration javaClientConfig = new JavaClientGeneratorConfiguration();
		javaClientConfig.setConfigurationType("XMLMAPPER");
		javaClientConfig.setTargetPackage(String.format("es.ise.%s.mappers", project));
		javaClientConfig.setTargetProject(srcDir.getAbsolutePath());
		return javaClientConfig;
	}
	
	private static void setupTable(File tablas, Context context)
	{
		try
		{
			ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(tablas));
			TableConfigurationLexer lexer = new TableConfigurationLexer(input);
			CommonTokenStream tokens = new CommonTokenStream(lexer);
			TableConfigurationParser parser = new TableConfigurationParser(tokens, context);
			parser.configuration();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}
